package com.tianji.chat.service.impl;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.client.remark.RemarkClient;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.chat.constants.ApiKeys;
import com.tianji.chat.domain.dto.ChatDTO;
import com.tianji.chat.domain.dto.FileDTO;
import com.tianji.chat.domain.po.Chat;
import com.tianji.chat.domain.query.ChatPageQuery;
import com.tianji.chat.domain.vo.ChatVO;
import com.tianji.chat.mapper.ChatMapper;
import com.tianji.chat.service.IChatService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.chat.service.IFileService;
import com.tianji.chat.service.ITaskService;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.FileSystemDocumentLoader;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.openai.OpenAiChatModel;

import dev.langchain4j.model.openai.OpenAiTokenizer;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.core.io.ResourceLoader;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.common.constants.Constant.DATA_FIELD_NAME_CREATE_TIME;
import static com.tianji.common.constants.Constant.DATA_FIELD_NAME_LIKED_TIME;
import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
import static java.util.stream.Collectors.joining;

/**
 * <p>
 * 聊天记录表 服务实现类
 * </p>
 *
 * @author pku-hlp
 * @since 2024-01-13
 */
@Service
@RequiredArgsConstructor
public class ChatServiceImpl extends ServiceImpl<ChatMapper, Chat> implements IChatService {

    private final ITaskService taskService;
    private final IFileService fileService;

    private final UserClient userClient;
    private final RemarkClient remarkClient;
    private final RabbitMqHelper mqHelper;

    private final ResourceLoader resourceLoader;


    @Override
    @Transactional
    public ChatVO saveReply(ChatDTO chatDTO) {
        // 1.获取登录用户
        Long userId = UserContext.getUser();
        // 2.新增回答
        Chat reply = BeanUtils.toBean(chatDTO, Chat.class);
        // 获取答案
        Long taskId = chatDTO.getTaskId();
        Long courseId = taskService.queryTaskById(taskId).getCourseId();
        if (courseId==0L){
            //如果为0，表示为不使用知识文件对话
            String answer=answer_from_gpt(reply.getQuery());
            reply.setResponse(answer);
        }else {
            String answer=answer_from_doc(reply.getQuery(),courseId);
            reply.setResponse(answer);
        }
        // 3.存储到数据库
        save(reply);
        ChatVO vo= BeanUtils.toBean(reply, ChatVO.class);
        return vo;
    }

    @Override
    public PageDTO<ChatVO> queryReplyPage(ChatPageQuery query) {
        // 1.先做参数判断
        Long taskId = query.getTaskId();
        
        if (taskId == null ) {
            throw new BadRequestException("任务id不能都为空");
        }
        // 标记当前是查询任务下的回答
        boolean isQueryAnswer = taskId != null;
        // 2.分页查询reply
        Page<Chat> page = lambdaQuery()
                .eq(isQueryAnswer, Chat::getTaskId, taskId)
                .page(query.toMpPage("update_time", false));
        List<Chat> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 3.数据处理，需要查询
        Set<Long> userIds = new HashSet<>();

        // 3.1.查询用户
        Map<Long, UserDTO> userMap = new HashMap<>(userIds.size());
        if(userIds.size() > 0) {
            List<UserDTO> users = userClient.queryUserByIds(userIds);
            userMap = users.stream().collect(Collectors.toMap(UserDTO::getId, u -> u));
        }

        // 4.处理VO
        List<ChatVO> list = new ArrayList<>(records.size());
        for (Chat r : records) {
            // 4.1.拷贝基础属性
            ChatVO v = BeanUtils.toBean(r, ChatVO.class);
            list.add(v);

        }
        return new PageDTO<>(page.getTotal(), page.getPages(), list);
    }

    //直接回答问题
    private String answer_from_gpt(String query){
        // Create an instance of a model
        ChatLanguageModel model = OpenAiChatModel.withApiKey(ApiKeys.OPENAI_API_KEY);

        // Start interacting
        String answer = model.generate(query);

        return answer;
    }

    //从知识文件中回答问题
    private String answer_from_doc(String query,Long courseId) {
        // Load the document that includes the information you'd like to "chat" about with the model.

        //TODO
        // 从腾讯云或者阿里云获取文件URL
//      根据本地服务器上的对应文档库中文件的id获取知识文件


        FileDTO fileDTO = fileService.getFileInfo(courseId);
        String path = fileDTO.getPath();

        Resource resource = resourceLoader.getResource(path);
//        Resource resource = resourceLoader.getResource("http://example.com/file.txt");
        Path filePath;
        //            filePath = Paths.get(resource.getFile().getPath());
        filePath=Paths.get(path);

        Document document = FileSystemDocumentLoader.loadDocument(filePath);


        // Split document into segments 100 tokens each
        DocumentSplitter splitter = DocumentSplitters.recursive(
                100,
                0,
                new OpenAiTokenizer(GPT_3_5_TURBO)
        );
        List<TextSegment> segments = splitter.split(document);

        // Embed segments (convert them into vectors that represent the meaning) using embedding model
        EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

        // Store embeddings into embedding store for further search / retrieval
        EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        embeddingStore.addAll(embeddings, segments);

        // Specify the question you want to ask the model
//        String question = "Who is Charlie?";


        // Embed the question
        Embedding questionEmbedding = embeddingModel.embed(query).content();

        // Find relevant embeddings in embedding store by semantic similarity
        // You can play with parameters below to find a sweet spot for your specific use case
        int maxResults = 3;
        double minScore = 0.7;
        List<EmbeddingMatch<TextSegment>> relevantEmbeddings
                = embeddingStore.findRelevant(questionEmbedding, maxResults, minScore);

        // Create a prompt for the model that includes question and relevant embeddings
        PromptTemplate promptTemplate = PromptTemplate.from(
                "Answer the following question to the best of your ability:\n"
                        + "\n"
                        + "Question:\n"
                        + "{{question}}\n"
                        + "\n"
                        + "Base your answer on the following information:\n"
                        + "{{information}}");

        String information = relevantEmbeddings.stream()
                .map(match -> match.embedded().text())
                .collect(joining("\n\n"));

        Map<String, Object> variables = new HashMap<>();
        variables.put("question", query);
        variables.put("information", information);

        Prompt prompt = promptTemplate.apply(variables);

        // Send the prompt to the OpenAI chat model
        ChatLanguageModel chatModel = OpenAiChatModel.withApiKey(ApiKeys.OPENAI_API_KEY);
        AiMessage aiMessage = chatModel.generate(prompt.toUserMessage()).content();

        // See an answer from the model
        String answer = aiMessage.text();
//        System.out.println(answer); // Charlie is a cheerful carrot living in VeggieVille...
        return answer;
    }


}
